Skip to content
标签
spring
字数
4074 字
阅读时间
18 分钟

一、SpringBoot介绍

  • Spring Boot 设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。
  • 嵌入的 Tomcat,无需部署 WAR 文件
  • Spring Boot 并不是对 Spring 功能上的增强,而是提供了一种快速使用 Spring 的方式

项目脚手架搭建地址:官方 阿里云

springboot启动流程

SpringBoot启动流程

二、构建springboot项目

2.1 引入依赖

maven方式

xml
<!-- 继承SpringBoot官方指定的父工程 -->	
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>1.5.8.RELEASE</version>
</parent>
<!-- springBoot的启动器 --> 
<dependency>
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId> 
</dependency>

gradle方式

groovy
buildscript {
    ext {
        //统一配置springcloud的版本号
        set('springCloudVersion', 'Greenwich.SR2')
    }
    dependencies {
        //引用boot的gradle插件
        classpath "org.springframework.boot:spring-boot-gradle-plugin:2.1.3.RELEASE"
    }
}

    //控制子项目依赖的jar包的版本号(多模块项目需要)
dependencyManagement {
        imports {
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
    }

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

2.2 编写启动类

java
/*启动器存放的位置。启动器可以和 controller 位于同一个包下,或
 * 者位于 controller 的上一级 包中,但是不能放到 controller 
 * 的平级以及子包下。
 */
@SpringBootApplication 
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args); 
    } 
}

三、基础知识

3.1 依赖说明

  • parent

    开发SpringBoot程序要继承spring-boot-starter-parent。使用parent可以帮助开发者进行版本的统一管理,parent包含了使用技术的依赖和版本号,但不负责帮助导入坐标,只依赖版本。

  • starter

    starter定义了使用某种技术时对于依赖的固定搭配格式,使用starter可以帮助开发者减少依赖配置,快速配置依赖关系。

  • 引导类

    SpringBoot程序启动还是创建了一个Spring容器对象。这个类在SpringBoot程序中是所有功能的入口,称这个类为

    作为一个引导类最典型的特征就是当前类上方声明了一个注解@SpringBootApplication

  • 内嵌tomcat

    内嵌tomcat是在spring-boot-starter-web依赖中引入的,依赖项为:spring-boot-starter-tomcat

    tomcat服务器运行其实是以对象的形式在Spring容器中运行的

    更换内置的服务器

    SpringBoot提供了3款内置的服务器

    • tomcat(默认):apache出品,粉丝多,应用面广,负载了若干较重的组件

    • jetty:更轻量级,负载性能远不及tomcat

    • undertow:负载性能勉强跑赢tomcat

    xml
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    </dependencies>

3.2 程序打包及运行

程序打包

SpringBoot程序不仅将项目的内容进行了打包,还把工程运行需要的jar包也打包,保证可以独立运行。

mvn项目需要添加sptingboot项目打包配置

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

jar包执行

jar包执行根据jar包中的MANIFEST.MF进行执行

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: springboot_08_ssmp
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.itheima.SSMPApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.5.4
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

使用java -jar执行此程序包,将执行Main-Class属性配置的类

Main-Class: org.springframework.boot.loader.JarLauncher。org.springframework.boot.loader.JarLauncher类是内部要查找Start-Class属性中配置的类。并执行对应的类

3.3 属性加载优先级

sh
java –jar springboot.jar –-server.port=80 --logging.level.root=debug

–-server.port=80 和 --logging.level.root=debug 为临时属性,可以快速修改某些配置。加载优先级要高于配置文件。官方属性加载的优先级:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

有14种配置方式。第3条Config data说的就是使用配置文件,第11条Command line arguments说的就是使用命令行临时参数。而这14种配置的顺序就是SpringBoot加载配置的顺序。上面的优先级低,下面的优先级高。

临时属性就是在调用main方法时向main方法中传递的args值。

3.4 配置文件种类及加载优先级

springboot支持三种格式的配置文件:properties格式、yml格式、yaml格式

加载优先级(谁生效)

application.properties  >  application.yml  >  application.yaml

Springboot程序启动时,会从以下位置加载配置文件:

\1. file:./config/:当前项目下的/config目录下

\2. file:./ :当前项目的根目录

\3. classpath:/config/:classpath的/config目录

\4. classpath:/ :classpath的根目录

加载顺序为上文的排列顺序,高优先级配置的属性会生效

file :config/application.yml  > file :application.yml  >  classpath:config/application.yml  >   classpath:application.yml

3.5 多环境配置

多环境配置文件

  • yaml方式

    yaml支持单文件分组环境和多文件方式

    yml
    spring:
    	profiles:
    		active: pro		# 启动pro  老版
    spring:
    	config:
        	activate:
            	on-profile: pro # 最新的格式
    ---
    spring:
    	profiles: pro
    server:
    	port: 80
    ---
    spring:
    	profiles: dev
    server:
    	port: 81
    ---
    spring:
    	profiles: test
    server:
    	port: 82

    还可定义多个文件,如:application-pro.yaml、application-dev.yaml。文件的命名规则为:application-环境名.yml。

    application是主配置文件,其中根据spring.profiles.active=环境后缀选择引入的配置文件。

  • properties方式

    properties支支持多文件方式,同yaml多文件方式一致。


多环境配置分隔

配置还可根据配置信息的类型分隔为不同类型的配置文件,如数据库,redis等,命名为application-类型.yml

导入配置则使用

yaml
spring:
	profiles:
    	active: dev  # 对应环境
        include: devDB,devRedis,devMVC  # 引入分离的配置

当有其他环境是,还可进行分组,从sptingboot2.4版本开始

yaml
spring:
	profiles:
    	active: dev
        group:
        	"dev": devDB,devRedis,devMVC
      		"pro": proDB,proRedis,proMVC
      		"test": testDB,testRedis,testMVC

多环境开发控制

  • maven方式

    在项目构建时,可根据环境来管理整个工程,选择保留的配置环境。

    xml
    <profiles>
        <profile>
            <id>env_dev</id>
            <properties>
                <profile.active>dev</profile.active>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>		<!--默认启动环境-->
            </activation>
        </profile>
        <profile>
            <id>env_pro</id>
            <properties>
                <profile.active>pro</profile.active>
            </properties>
        </profile>
    </profiles>

    sptingboot读取maven中属性值

    yaml
    spring:
    	profiles:
        	active: @profile.active@

    启动时选择环境

    项目启动时加载的application配置文件,可以修改名称。自定义加载的配置文件的文件名

    通过临时属性修改。

    sh
    --spring.config.name=文件名(不包含后缀)
    
    --spring.config.location=classpath:全路径+文件名+后缀,多个用,号隔开。

    3.6 bean属性赋值

    @ConfigurationProperties注解是用来为bean绑定属性的。添加到类上是为spring容器管理的当前类的对象绑定属性,添加到方法上是为spring容器管理的当前方法的返回值对象绑定属性,其实本质上都一样。

    开发者可以在yml配置文件中以对象的格式添加若干属性,在实体类上添加该注解,即可将配置中的属性值注入到对象中,该实体类需要交由spring管理

    为bean属性赋值

    java
    @Component
    @Data
    @ConfigurationProperties(prefix = "servers")
    public class ServerConfig {
        private String ipAddress;
        private int port;
        private long timeout;
    }

    为第三方bean加载属性。

    java
    @Bean
    @ConfigurationProperties(prefix = "datasource")
    public DruidDataSource datasource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }

    @EnableConfigurationProperties

    专门标注使用@ConfigurationProperties注解绑定属性的bean是哪些

    java
    @SpringBootApplication
    @EnableConfigurationProperties(ServerConfig.class)
    public class Springboot13ConfigurationApplication {
    }

    使用该注解标注后,在bean上不用再声明@Component注解了。

    解决使用@ConfigurationProperties注解时,有提示信息的问题

    添加依赖坐标

    xml
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>

    属性注入宽松绑定

    在进行匹配时,配置中的名称要去掉中划线和下划线后,忽略大小写的情况下去与java代码中的属性名进行忽略大小写的等值匹配,相同则匹配成功。但如果设置的prefix的为驼峰命名,取配置时会报错。

    常用剂量单位绑定

    配置中的值,没有单位的概念,因此,查看配置时,可能会存在误解

    springboot充分利用了JDK8中提供的全新的用来表示计量单位的新数据类型

    java
    @Component
    @Data
    @ConfigurationProperties(prefix = "servers")
    public class ServerConfig {
        // 表示时间间隔,可以通过@DurationUnit注解描述时间单位,小时
        @DurationUnit(ChronoUnit.HOURS)
        // 表示存储空间,可以通过@DataSizeUnit注解描述存储空间单位,MB
        private Duration serverTimeOut;
        @DataSizeUnit(DataUnit.MEGABYTES)
        private DataSize dataSize;
    }

3.6 Condition条件判断

Condition(条件):Condition是在Spring4.0增加的条件判断功能,通 过这个可以功能可以实现选择性的创建Bean操作。

自定义注解实现某个类存在时才加载

  • 自定义注解

    import org.springframework.context.annotation.Conditional;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(ClassCondition.class)
    public @interface MyConditionalOnClass {
    
        String[] value();
    }
  • 自定义控制加载类

    java
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    import java.util.Map;
    
    public class ClassCondition implements Condition {
    
        /**
         *
         * @param conditionContext  上下文对象,获取Bean工厂,获取ClassLoader
         * @param annotatedTypeMetadata  注解元对象,用于获取注解信息
         * @return
         */
    
        @Override
        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
            //返回是false时,Bean不加载的
            //返回是true时,Bean加载的
    
            Map<String, Object> map = annotatedTypeMetadata.getAnnotationAttributes(MyConditionalOnClass.class.getName());
            System.out.println(map);
    
            boolean flag = true;
            try {
                String[] strings = (String[]) map.get("value");
                for (String className : strings) {
                    Class.forName(className);
                }
            } catch (ClassNotFoundException e) {
                flag = false;
            }
            return flag;
        }
    }
  • 使用

    java
     //将User实体类放入Bean
    //1.如果没有条件,User对象放入Bean里
    //2.加入Condition条件
    //  @Conditional注解里,需要导入Condition实现类
    //  加载一个User,创建一个Condition实现类
    //3.不应该写死"redis.clients.jedis.Jedis",在Condition注解里导入
    
    //@Conditional(ClassCondition.class)
    @Bean
    //@MyConditionalOnClass({"redis.clients.jedis.Jedis"})
    
    @ConditionalOnProperty(name = "itcast", havingValue = "itheima") // 值对应时才加载。
    public User user(){
        return new User();
    }

总结

自定义条件:

  • 自定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回boolean值 。

    matches 方法两个参数:

    • context:上下文对象,可以获取属性值,获取类加载器,获取FactoryBean等。
    • metadata:元数据对象,用于获取注解属性。
  • 判断条件:在初始化Bean时,使用@Conditional(条件类.class)注解。

SpringBoot常用条件注解:

  • ConditionalOnProperty:判断配置文件中是否有对应的属性和值才初始化Bean。
  • ConditionalOnClass:判断环境中是否有对应的字节码文件才初始化Bean。
  • ConditionalOnMissingBean:判断环境中是否有对应的Bean才初始化Bean。

3.7 @Enable*注解

SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某 些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动 态加载

当@ComponentScan扫描不到加载的类时(扫描范围:当前引导类所在包以其子包 ),可以标注增加扫描包的范围@ComponentScan("com.itheima.config")或使用@Import注解导入配置类。(配置类中加载@Bean)

对import进行封装

java
@Target({ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@Import(UserConfig.class)
public @interface EnableUser {}
// 在启动类上添加@EnableUser即可加载配置

3.8 @Import注解

@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:

  • 导入Bean
  • 导入配置类
  • 导入ImportSelector实现类,一般用于加载配置文件中的类
  • 导入ImportBeanDefifinitionRegistrar实现类

导入Bean

java
@SpringBootApplication
@Import(User.class) 
public class SpringbootEnableApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
        /*
        Object user = context.getBean("user");
        System.out.println(user);
        */
        User user = context.getBean(User.class);
        System.out.println(user);
        // 如果想要获取Bean的名称,那么可以使用context.getBeansOfType
        Map<String, User> map = context.getBeansOfType(User.class); 
        System.out.println(map);
    }
}

导入配置类

// UserConfig
@Bean 
public Role role(){ return new Role(); }

// 启动类上添加 @Import(UserConfig.class)

实现ImportSelector接口

java
public class MyImportSelector implements ImportSelector { 
    @Override 
    public String[] selectImports(AnnotationMetadata annotationMetadata) { 
        return new String[]{"com.itheima.domain.Role", "com.itheima.domain.User"};
    }
}

// 修改启动类
@SpringBootApplication 
@Import(MyImportSelector.class)
public class SpringbootEnableApplication {}

实现ImportBeanDefinitionRegistrar接 口

java
// 创建一个MyImportBeanDefinitionRegistrar类,实现ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions方法
@Override 
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 
    //注册User类 
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).get BeanDefinition(); 
    registry.registerBeanDefinition("user", beanDefinition); 
    //注册Role类
    beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Role.class).get BeanDefinition(); 
    registry.registerBeanDefinition("role", beanDefinition); 
}
// 修改启动类
@SpringBootApplication 
@Import(MyImportBeanDefinitionRegistrar.class) 
public class SpringbootEnableApplication {}

3.9 @EnableAutoConfiguration

@SpringBootApplication中的@EnableAutoConfiguration注解,也是通过@Import({AutoConfigurationImportSelector.class})来实现类的加载,

配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化 Bean。

并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean。

3.10 ImportSelector

ImportSelector接口是Spring导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)中起到了决定性的作用。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。

DeferredImportSelector接口继承ImportSelector,他和ImportSelector的区别在于装载bean的时机上,DeferredImportSelector需要等所有的@Configuration都执行完毕后才会进行装载

ImportSelector导入的Bean是用编程的方式动态的载入bean,在ConfifigurationClassParser这个类的processImports方法中处理

springBoot自动装载很大程度上归功于ImportSelector。

SpringBootApplication中声明了一个 @EnableAutoConfiguration。其中通过Import引入了SpringBoot定义的AutoConfigurationImportSelector,最终获取bean的渠道在SpringFactoriesLoader.loadFactoryNames,方法中会获取工程中所有META-INF/spring.factories文件,将其中的键值组合成Map并加载类。

每个jar都可以定义自己的META-INF/spring.factories ,jar被加载的同时 spring.factories里面定义的bean就可以自动被加载

3.11 监听

监听机制

SpringBoot 的监听机制,其实是对Java提供的事件监听机制的封装。

Java中的事件监听机制定义了以下几个角色:

①事件:Event,继承 java.util.EventObject 类的对象

②事件源:Source ,任意对象Object

③监听器:Listener,实现 java.util.EventListener 接口的对象

Springboot监听器

SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。

一共有四种实现方法:

  • ApplicationContextInitializer
  • SpringApplicationRunListener
  • CommandLineRunner
  • ApplicationRunner

创建Listener模块

ApplicationContextInitializer

java
@Component 
public class MyApplicationContextInitializer implements ApplicationContextInitializer { 
    @Override 
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) { 
        System.out.println("ApplicationContextInitializer...ini tialize"); 
    } 
}

META-INF/spring.factories 中添加配置

properties
org.springframework.context.ApplicationContextInitialize r=com.itheima.listener.listener.MyApplicationContextInit ializer

SpringApplicationRunListener

java
//@Component 
public class MySpringApplicationRunListener implements SpringApplicationRunListener { 
    // 需要有参构造
    public MySpringApplicationRunListener(SpringApplication application, String[] args) {}
    @Override 
    public void starting() { 
        System.out.println("SpringApplicationRunListener...正在 启动");
    }
    @Override 
    public void environmentPrepared(ConfigurableEnvironment environment) {
        System.out.println("SpringApplicationRunListener...环境 准备中");
    }
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener...上下 文准备");
    }
    @Override 
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener...上下 文加载"); 
    }
    @Override 
    public void started(ConfigurableApplicationContext context) { 
        System.out.println("SpringApplicationRunListener...已经 启动");
    }
    @Override 
    public void running(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener...正在 启动中");
    }
    @Override 
    public void failed(ConfigurableApplicationContext context, Throwable exception) { 
        System.out.println("SpringApplicationRunListener...启动 失败");
    }
}

修改 META-INF/spring.factories 配置文件

properties
org.springframework.boot.SpringApplicationRunListener=co m.itheima.listener.listener.MySpringApplicationRunListener

CommandLineRunner

java
@Component 
public class MyCommandLineRunner implements CommandLineRunner { 
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner...run");
    } 
}

ApplicationRunner

java
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override 
    public void run(ApplicationArguments args) throws Exception { 
        System.out.println("ApplicationRunner...run");
    } 
}

ApplicationStartedEvent继承自SpringApplicationEvent

SpringApplicationEvent继承自ApplicationEvent

ApplicationEvent继承自EventObject

证明:Springboot里的监听事件是对java监听事件的一个封装

四、拓展

4.1 自定义Starter

Starter命名规范

starter所属命名规则示例
官方提供spring-boot-starter-技术名称spring-boot-starter-web
spring-boot-starter-test
第三方提供第三方技术名称-spring-boot-starterdruid-spring-boot-starter
第三方提供第三方技术名称-boot-starter(第三方技术名称过长,简化命名)mybatis-plus-boot-starter

定义步骤 以redis为例:

在Starter项目中添加依赖

xml
<dependency>
    <groupId>redis.clients</groupId> 
    <artifactId>jedis</artifactId>
</dependency>

创建RedisAutoConfigure配置类

java
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;

@Configuration
@ConditionalOnClass(Jedis.class)//加载的时候判断Jedis类存在的时候才加载Bean
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfigure {

    @Bean
    @ConditionalOnMissingBean(name = "jedis")// 当jedis没有被创建时加载这个类,并写入Bean
    public Jedis jedis(RedisProperties redisProperties){
        System.out.println("RedisAutoConfigure...jedis");
        return new Jedis(redisProperties.getHost(), redisProperties.getPort());
    }
}

创建redis配置类

java
import org.springframework.boot.context.properties.ConfigurationProperties;


//spring.redis.host=127.0.0.1
//spring.redis.port=6379

@ConfigurationProperties(prefix = "redis")
public class RedisProperties {

    private String host = "localhost";
    private int port = 6379;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}

在resources下新建META-INF/spring.factories

properties
org.springframework.boot.autoconfigure.EnableAutoConfigu ration=com.itheima.redis.RedisAutoConfigure